package com.yanqu.road.server.gameplayer.module.activity.operamask;

import com.alibaba.fastjson.JSON;
import com.yanqu.road.entity.activity.ActivityInfo;
import com.yanqu.road.entity.activity.args.OperaMaskScenePingJiArgs;
import com.yanqu.road.entity.activity.args.OperaMaskSumScoreArgs;
import com.yanqu.road.entity.activity.operamask.OperaMaskConfig;
import com.yanqu.road.entity.activity.operamask.OperaMaskGameData;
import com.yanqu.road.entity.activity.operamask.OperaMaskSceneData;
import com.yanqu.road.entity.activity.operamask.OperaMaskUserData;
import com.yanqu.road.entity.activity.operamask.config.OperaMaskPaintBoxConfig;
import com.yanqu.road.entity.activity.operamask.config.OperaMaskSceneConfig;
import com.yanqu.road.entity.activity.operamask.config.OperaMaskSkillConfig;
import com.yanqu.road.entity.activity.operamask.game.OperaMaskBlock;
import com.yanqu.road.entity.activity.operamask.game.OperaMaskSkill;
import com.yanqu.road.entity.activity.operamask.sanxiao.OperaMaskBoom;
import com.yanqu.road.entity.activity.operamask.sanxiao.OperaMaskEliminateParam;
import com.yanqu.road.entity.activity.operamask.sanxiao.OperaMaskEliminateResult;
import com.yanqu.road.entity.activity.operamask.sanxiao.OperaMaskEliminateTip;
import com.yanqu.road.entity.activity.operamask.sanxiao.OperaMaskRoundResult;
import com.yanqu.road.entity.activity.operamask.sanxiao.OperaMaskSkillEffect;
import com.yanqu.road.entity.activity.operamask.sanxiao.OperaMaskStageResult;
import com.yanqu.road.entity.config.goods.MallInfo;
import com.yanqu.road.entity.enums.eGamePlayerEventType;
import com.yanqu.road.entity.enums.eLogMoneyType;
import com.yanqu.road.entity.enums.eOperaMaskBoomType;
import com.yanqu.road.entity.enums.eOperaMaskSkillType;
import com.yanqu.road.entity.enums.eShopType;
import com.yanqu.road.entity.log.LogOperaMaskEliminate;
import com.yanqu.road.entity.log.LogOperaMaskEndScene;
import com.yanqu.road.entity.log.LogOperaMaskEnergy;
import com.yanqu.road.entity.log.LogOperaMaskSkillStar;
import com.yanqu.road.entity.log.LogOperaMaskUnlockScene;
import com.yanqu.road.logic.bussiness.activity.OperaMaskBusiness;
import com.yanqu.road.logic.pb.OperaMaskPb2;
import com.yanqu.road.pb.activity.OperaMaskProto;
import com.yanqu.road.server.gameplayer.GamePlayer;
import com.yanqu.road.server.gameplayer.module.base.GeneralModule;
import com.yanqu.road.server.gameplayer.module.player.CurrencyModule;
import com.yanqu.road.server.manger.activity.ActivityMgr;
import com.yanqu.road.server.manger.activity.operamask.OperaMaskEliminateLogic;
import com.yanqu.road.server.manger.activity.operamask.OperaMaskMgr;
import com.yanqu.road.server.manger.activity.operamask.OperaMaskSkillLogic;
import com.yanqu.road.server.manger.log.AutoLogMgr;
import com.yanqu.road.server.pb.OperaMaskPb;
import com.yanqu.road.server.pb.PlayerPb;
import com.yanqu.road.server.protocol.ClientProtocol;
import com.yanqu.road.server.protocol.CrossProtocol;
import com.yanqu.road.server.protocol.GameErrorCode;
import com.yanqu.road.utils.config.Config;
import com.yanqu.road.utils.date.DateHelper;
import com.yanqu.road.utils.property.Property;
import com.yanqu.road.utils.property.PropertyHelper;
import com.yanqu.road.utils.string.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class OperaMaskModule extends GeneralModule {

    private OperaMaskUserData operaMaskUserData = null;

    public OperaMaskModule(GamePlayer player) {
        super(player);
    }

    private static Logger logger = LogManager.getLogger(OperaMaskModule.class.getName());

    @Override
    public boolean loadData() {
        OperaMaskConfig config = OperaMaskMgr.getConfig();
        if (config != null && ActivityMgr.activityInShowTime(config.getActivityInfo())) {
            operaMaskUserData = OperaMaskBusiness.getOperaMaskUserData(config.getActivityInfo().getActivityId(), getUserId());
        }
        return true;
    }

    @Override
    public boolean afterLoadData() {
        return true;
    }

    @Override
    public boolean saveData() {
        if (operaMaskUserData != null) {
            if (operaMaskUserData.isInsertOption()) {
                OperaMaskBusiness.addOperaMaskUserData(operaMaskUserData);
            } else if (operaMaskUserData.isUpdateOption()) {
                OperaMaskBusiness.updateOperaMaskUserData(operaMaskUserData);
            }
        }
        return true;
    }

    @Override
    public void afterLogin() {
        refreshData();
    }

    public void refreshData() {
        initUserData();
        syncConfig();
        syncUserData();

        OperaMaskConfig config = OperaMaskMgr.getConfig();
        if (operaMaskUserData != null && config != null) {
            notifyScoreChangeCondition(config, operaMaskUserData, null);
        }
    }

    public synchronized OperaMaskUserData getUserData() {
        if (operaMaskUserData != null) {
            OperaMaskConfig config = OperaMaskMgr.getConfig();
            if (config != null) {

                int logOldEnergy = operaMaskUserData.getEnergy();

                // 检查每日重置
                if (operaMaskUserData.getRecoveryTime() < DateHelper.getTodayZeroTimeStamp()) {
                    operaMaskUserData.setEnergy(config.getMaxEnergy());
                    operaMaskUserData.setRecoveryTime(System.currentTimeMillis());
                }

                // 日志
                if (logOldEnergy != operaMaskUserData.getEnergy()) {
                    AutoLogMgr.add(new LogOperaMaskEnergy(
                            getUserId(),
                            config.getActivityInfo().getActivityId(),
                            LogOperaMaskEnergy.ENERGY_TYPE_RECOVERY,
                            operaMaskUserData.getEnergy() - logOldEnergy,
                            operaMaskUserData.getEnergy()
                    ));
                }

            }
        }
        return operaMaskUserData;
    }

    /**
     * 初始化玩家数据
     */
    private synchronized void initUserData() {
        OperaMaskConfig config = OperaMaskMgr.getConfig();
        // 没活动
        if (config == null || !ActivityMgr.activityInShowTime(config.getActivityInfo())) {
            return;
        }
        // 已有数据
        if (operaMaskUserData != null && operaMaskUserData.getActivityId() == config.getActivityInfo().getActivityId()) {
            return;
        }

        // 生成关卡记录
        Map<Integer, OperaMaskSceneData> sceneDataMap = new ConcurrentHashMap<>();
        for (OperaMaskSceneConfig sceneConfig : config.getSceneConfigMap().values()) {
            OperaMaskSceneData sceneData = new OperaMaskSceneData();
            sceneData.setId(sceneConfig.getSceneId());
            // 解锁初始关
            if (sceneConfig.getUnlock().equals("0")) {
                sceneData.setStatus(OperaMaskSceneData.STATUS_UNLOCK);
            }
            sceneDataMap.put(sceneData.getId(), sceneData);
        }

        // 生成数据
        OperaMaskUserData tmpUserData = new OperaMaskUserData();
        tmpUserData.setActivityId(config.getActivityInfo().getActivityId());
        tmpUserData.setUserId(getUserId());
        tmpUserData.setSingLevel(1);
        tmpUserData.setEnergy(config.getMaxEnergy());
        tmpUserData.setRecoveryTime(System.currentTimeMillis());
        tmpUserData.setGameData(null);
        tmpUserData.setSceneDataMap(sceneDataMap);
        tmpUserData.setInsertOption();

        // 替换数据
        operaMaskUserData = tmpUserData;
    }


    /**
     * 解锁关卡
     *
     * @return true新解锁成功
     */
    private boolean tryUnlockScene(OperaMaskUserData data, int sceneId) {
        OperaMaskConfig config = OperaMaskMgr.getConfig();
        if (config == null) {
            log.info("opera mask, userId {}, tryUnlockScene {} fail, no config.", getUserId(), sceneId);
            return false;
        }
        OperaMaskSceneConfig sceneConfig = config.getSceneConfigMap().get(sceneId);
        if (sceneConfig == null) {
            log.info("opera mask, activityId {}, userId {}, tryUnlockScene {} fail, no scene config.", config.getActivityInfo().getActivityId(), getUserId(), sceneId);
            return false;
        }

        // 已经解锁
        Map<Integer, OperaMaskSceneData> sceneDataMap = data.getSceneDataMap();
        if (sceneDataMap.get(sceneId).getStatus() != OperaMaskSceneData.STATUS_LOCK) {
            return false;
        }

        // 判断解锁条件
        boolean canUnlock = true;
        if (sceneConfig.getUnlock().equals("-1")) {
            return false;
        }
        for (Map.Entry<Integer, Long> entry : sceneConfig.getUnlockMap().entrySet()) {
            Integer needSceneId = entry.getKey();
            Long needScore = entry.getValue();
            if (sceneDataMap.get(needSceneId).getScore() < needScore) {
                canUnlock = false;
                break;
            }
        }
        if (!canUnlock) {
            return false;
        }

        // 解锁
        OperaMaskSceneData sceneData = sceneDataMap.get(sceneId);
        sceneData.setStatus(OperaMaskSceneData.STATUS_NEW_UNLOCK);

        // 日志
        AutoLogMgr.add(new LogOperaMaskUnlockScene(
                getUserId(),
                config.getActivityInfo().getActivityId(),
                sceneData.getId(),
                data.getSingLevel(),
                data.getSumEnergy(),
                getSumScore()
        ));

        return true;
    }

    /**
     * 下发配置
     */
    private void syncConfig() {
        OperaMaskConfig config = OperaMaskMgr.getConfig();
        if (config == null || !ActivityMgr.activityInShowTime(config.getActivityInfo())) {
            return;
        }
        OperaMaskProto.OperaMaskSyncConfigRespMsg.Builder builder = OperaMaskPb.parseOperaMaskSyncConfigRespMsg(config);
        player.sendPacket(ClientProtocol.U_OPERA_MASK_SYNC_CONFIG, builder);
    }

    /**
     * 下发数据
     */
    public void syncUserData() {
        OperaMaskConfig config = OperaMaskMgr.getConfig();
        if (config == null || !ActivityMgr.activityInShowTime(config.getActivityInfo())) {
            return;
        }
        OperaMaskUserData userData = getUserData();
        if (userData == null) {
            return;
        }
        OperaMaskProto.OperaMaskSyncUserDataRespMsg.Builder builder = OperaMaskProto.OperaMaskSyncUserDataRespMsg.newBuilder();
        builder.setUserData(OperaMaskPb.parseOperaMaskUserDataMsg(userData));
        player.sendPacket(ClientProtocol.U_OPERA_MASK_SYNC_USER_DATA, builder);
    }

    /**
     * 生成一份游戏数据
     */
    private OperaMaskGameData createGameData(OperaMaskUserData userData, int sceneId, int sceneRank) {
        OperaMaskConfig config = OperaMaskMgr.getConfig();
        OperaMaskSceneConfig sceneConfig = config.getSceneConfigMap().get(sceneId);

        OperaMaskSceneData sceneData = userData.getSceneDataMap().get(sceneId);
        boolean isFirst = sceneData == null || sceneData.getScore() <= 0;

        // 第一次玩初始油彩盒进度加到差3个升级
        int initBoxValue = 0;
        long sumScore = userData.getSceneDataMap().values().stream().mapToLong(OperaMaskSceneData::getScore).sum();
        if (sumScore == 0) {
            initBoxValue = sceneConfig.getPaintBoxConfigMap().get(1).getUpgradeCost() - 3;
        }

        OperaMaskGameData gameData = new OperaMaskGameData();
        gameData.setSceneId(sceneId);
        gameData.setChooseSkill(new ArrayList<>());
        gameData.setBlockList(isFirst ? OperaMaskMgr.getInitBlockList(sceneConfig) : OperaMaskMgr.getRandomBlockList(sceneConfig));
        gameData.setLeftStep(sceneConfig.getStepNum());
        gameData.setBoxValue(initBoxValue);
        gameData.setBeginTime(System.currentTimeMillis());
        gameData.setSkillAddStep(0);
        gameData.setOldTopScore(sceneData == null ? 0 : sceneData.getScore());
        gameData.setOldSceneRank(sceneRank);
        gameData.setAllRandomTimes(0);
        return gameData;
    }

    /**
     * 判断游戏结束
     */
    private boolean isGameOver(OperaMaskConfig config, OperaMaskGameData gameData) {
        // 步数为0
        if (gameData.getLeftStep() <= 0 && gameData.getSkillAddStep() <= 0) {
            return !isBoxUpgrade(config, gameData); // 没升级就结束
        }
        return false;
    }

    private boolean isBoxUpgrade(OperaMaskConfig config, OperaMaskGameData gameData) {
        Map<Integer, OperaMaskPaintBoxConfig> boxConfigMap = config.getSceneConfigMap().get(gameData.getSceneId()).getPaintBoxConfigMap();
        if (!boxConfigMap.containsKey(gameData.getBoxLevel())) {
            return false; // 满级了，无法继续升级
        }
        OperaMaskPaintBoxConfig boxConfig = boxConfigMap.get(gameData.getBoxLevel());
        int upgradeCost = getBoxUpgradeCost(config, boxConfig, gameData.getSkillList());
        if (gameData.getBoxValue() < upgradeCost) {
            return false; // 油彩盒进度不够升级
        }
        return true;
    }

    /**
     * 处理关卡结束
     */
    private OperaMaskProto.OperaMaskSceneEndMsg.Builder handleGameOver(OperaMaskConfig config, OperaMaskUserData userData, OperaMaskGameData gameData) {

        // 游戏数据清除
        userData.setGameData(null);

        // 获取评级奖励
        int sceneId = gameData.getSceneId();
        long score = gameData.getScore();
        String pingJiReward = OperaMaskMgr.getPingJiReward(config, sceneId, score);
        // 加奖励
        if (!StringUtils.isNullOrEmpty(pingJiReward)) {
            player.getModule(CurrencyModule.class).addCurrency(pingJiReward, eLogMoneyType.OperaMaskActivity, eLogMoneyType.OperaMaskActivityPassReward);
        }

        // 日志
        StringBuilder sb = new StringBuilder();
        for (OperaMaskSkill skill : gameData.getSkillList()) {
            sb.append(skill.getSkillId());
            sb.append("=");
            sb.append(skill.getSkillLevel());
            sb.append(";");
        }
        AutoLogMgr.add(new LogOperaMaskEndScene(
                getUserId(),
                config.getActivityInfo().getActivityId(),
                gameData.getSceneId(),
                sb.toString(),
                gameData.getScore(),
                userData.getSingLevel(),
                (System.currentTimeMillis() - gameData.getBeginTime()) / DateHelper.SECOND_MILLIONS,
                OperaMaskMgr.calcSkillValue(gameData.getScore(), userData.getSingLevel()),
                gameData.getBoxLevel(),
                pingJiReward
        ));

        // 检查解锁但没看过的剧目
        int unlockSceneId = 0;
        for (OperaMaskSceneData sceneData : userData.getSceneDataMap().values()) {
            if (sceneData.getStatus() == OperaMaskSceneData.STATUS_NEW_UNLOCK) {
                unlockSceneId = sceneData.getId();
            }
        }

        gameData.setNewRecord(isNewRecord(gameData));
        // 记录返回值
        OperaMaskProto.OperaMaskSceneEndMsg.Builder endBuilder = OperaMaskProto.OperaMaskSceneEndMsg.newBuilder();
        endBuilder.setStep(gameData.getUseStep());
        endBuilder.setScore(gameData.getScore());
        endBuilder.setTopScore(Math.max(gameData.getOldTopScore(), gameData.getScore()));
        endBuilder.setNewRecord(gameData.isNewRecord());
        endBuilder.setReward(pingJiReward);
        endBuilder.setOldRank(gameData.getOldSceneRank());
        endBuilder.setNewRank(-1);
        endBuilder.setUnlockScene(unlockSceneId);
        endBuilder.setSkillValue(OperaMaskMgr.calcSkillValue(gameData.getScore(), userData.getSingLevel()));
        return endBuilder;
    }

    /**
     * 选择技能后触发效果
     */
    private void handleChooseSkillEffect(OperaMaskSkill skill, List<OperaMaskSkillEffect> skillEffectList, OperaMaskUserData userData, OperaMaskConfig config) {
        OperaMaskGameData gameData = userData.getGameData();
        if (skill.getSkillId() == eOperaMaskSkillType.Skill03.getValue()) {
            // 横竖炸弹 -> 十字炸弹
            OperaMaskEliminateLogic.handleSkill03(gameData.getBlockList(), skillEffectList);
        } else if (skill.getSkillId() == eOperaMaskSkillType.Skill07.getValue()) {
            // 彩虹炸弹 -> 超级彩虹炸弹
            OperaMaskEliminateLogic.handleSkill07(gameData.getBlockList(), skillEffectList);
        } else if (skill.getSkillId() == eOperaMaskSkillType.Skill04.getValue() || skill.getSkillId() == eOperaMaskSkillType.Skill08.getValue()) {
            // 斜向消除/移动界面提示
            if (skill.getSkillLevel() == 1) {
                skillEffectList.add(new OperaMaskSkillEffect(skill.getSkillId()));
            }
        } else if (skill.getSkillId() == eOperaMaskSkillType.Skill09.getValue() && skill.getSkillLevel() == 1) {
            // 开始前概率替换炸弹
            int skill09Effect = OperaMaskSkillLogic.getSkill09Effect(gameData.getSkillList(), config);
            if (skill09Effect > 0) {
                OperaMaskEliminateLogic.handleSkill09(gameData.getBlockList(), gameData.getSkillList(), skillEffectList, skill09Effect, new Random(), config, gameData);
            }
        } else if (skill.getSkillId() == eOperaMaskSkillType.Skill10.getValue()) {
            // 收集油彩数
            skillEffectList.add(new OperaMaskSkillEffect(skill.getSkillId()));
        } else if (skill.getSkillId() == eOperaMaskSkillType.Skill19.getValue()) {
            // 炸弹换多倍油彩
            List<Integer> skill19Effect = OperaMaskSkillLogic.getSkill19Effect(gameData.getSkillList(), config);
            OperaMaskEliminateLogic.handleSkill19(gameData.getBlockList(), skillEffectList, skill19Effect, gameData);
        }
    }

    /**
     * 获取油彩盒升级消耗
     */
    private int getBoxUpgradeCost(OperaMaskConfig config, OperaMaskPaintBoxConfig boxConfig, List<OperaMaskSkill> skillList) {
        return Math.max(0, boxConfig.getUpgradeCost() * (1000 - OperaMaskSkillLogic.getSkill10Effect(skillList, config)) / 1000);
    }

    // =========================================

    /**
     * 开启关卡
     */
    public int startScene(int sceneId, int oldSceneRank) {
        OperaMaskConfig config = OperaMaskMgr.getConfig();
        OperaMaskUserData userData = getUserData();

        if (userData.getGameData() != null && userData.getGameData().getSceneId() != sceneId) {
            return GameErrorCode.E_OPERA_MASK_SCENE_PLAYING;
        }

        // 【继续游戏】
        if (userData.getGameData() != null) {
            return 0;
        }

        // 【新关卡】

        OperaMaskSceneConfig sceneConfig = config.getSceneConfigMap().get(sceneId);
        if (sceneConfig == null) {
            return GameErrorCode.E_OPERA_MASK_SCENE_NO_EXIST;
        }
        Map<Integer, OperaMaskSceneData> sceneDataMap = userData.getSceneDataMap();
        if (sceneDataMap.get(sceneId) == null || sceneDataMap.get(sceneId).getStatus() == OperaMaskSceneData.STATUS_LOCK) {
            return GameErrorCode.E_OPERA_MASK_SCENE_NO_UNLOCK;
        }

        // 跨服拿排名才能继续
        if (oldSceneRank == -666) {
            OperaMaskProto.CrossOperaMaskStartSceneMsg.Builder builder = OperaMaskProto.CrossOperaMaskStartSceneMsg.newBuilder();
            builder.setActivityId(config.getActivityInfo().getActivityId());
            builder.setSceneId(sceneId);
            player.sendPacket(CrossProtocol.C2_OPERA_MASK_START_SCENE, builder);
            return -777;
        }

        // 生成一份数据
        OperaMaskGameData gameData = createGameData(userData, sceneId, oldSceneRank);

        // 扣体力
        int costEnergy = 1;
        if (userData.getEnergy() >= costEnergy) {
            userData.setEnergy(userData.getEnergy() - costEnergy);
            // 日志
            AutoLogMgr.add(new LogOperaMaskEnergy(
                    getUserId(),
                    config.getActivityInfo().getActivityId(),
                    LogOperaMaskEnergy.ENERGY_TYPE_START_SCENE,
                    costEnergy,
                    userData.getEnergy()
            ));
        } else {
            Property cost = PropertyHelper.parseStringToProperty(config.getEnergyItemId() + "=1");
            if (!player.getModule(CurrencyModule.class).removeCurrency(cost, eLogMoneyType.OperaMaskActivity, eLogMoneyType.OperaMaskActivityUseEnergyItem)) {
                return GameErrorCode.E_OPERA_MASK_ENERGY_NO_ENOUGH;
            }
        }
        userData.setSumEnergy(userData.getSumEnergy() + 1);

        // 修改数据
        userData.setGameData(gameData);

        userData.setUpdateOption();

        return 0;
    }

    /**
     * 移动方块
     */
    public int moveBlock(int fromX, int fromY, int toX, int toY) {
        OperaMaskConfig config = OperaMaskMgr.getConfig();
        if (config == null || !ActivityMgr.activityInTime(config.getActivityInfo())) {
            return GameErrorCode.E_ACTIVITY_HAD_END;
        }
        OperaMaskUserData userData = getUserData();
        if (userData == null) {
            return GameErrorCode.E_OPERA_MASK_NO_UNLOCK;
        }
        OperaMaskGameData gameData = userData.getGameData();
        if (gameData == null) {
            return GameErrorCode.E_OPERA_MASK_SCENE_END;
        }

        Map<Integer, OperaMaskPaintBoxConfig> boxConfigMap = config.getSceneConfigMap().get(gameData.getSceneId()).getPaintBoxConfigMap();
        boolean boxMaxLevel = !boxConfigMap.containsKey(gameData.getBoxLevel());
        if (!boxMaxLevel) {
            OperaMaskPaintBoxConfig boxConfig = boxConfigMap.get(gameData.getBoxLevel());
            int boxUpgradeCost = getBoxUpgradeCost(config, boxConfig, gameData.getSkillList());
            if (gameData.getBoxValue() >= boxUpgradeCost) {
                return GameErrorCode.E_OPERA_MASK_NO_NEED_CHOOSE_SKILL;
            }
        }

        OperaMaskSceneData sceneData = userData.getSceneDataMap().get(gameData.getSceneId());

        // 准备参数
        OperaMaskEliminateParam param = new OperaMaskEliminateParam();
        param.setFromX(fromX);
        param.setFromY(fromY);
        param.setToX(toX);
        param.setToY(toY);
        param.setConfig(config);
        param.setGameData(gameData.clone()); // clone防止报错数据炸了
        param.setFirstMove(sceneData.getScore() == 0);

        // 【消除】
        OperaMaskEliminateResult result = OperaMaskEliminateLogic.moveBlock(param, config, true);
        if (result.getRet() != 0) {
            return result.getRet();
        }
        // 更新消除方块数
        gameData.setEliminateBlockAmount(param.getGameData().getEliminateBlockAmount());

        // 处理消除结果
        OperaMaskProto.OperaMaskResultMsg.Builder resultMsg = handleMoveResult(config, userData, gameData, result, true);
        OperaMaskProto.OperaMaskMoveBlockRespMsg.Builder respMsg = OperaMaskProto.OperaMaskMoveBlockRespMsg.newBuilder();
        respMsg.setRet(0);
        respMsg.setResult(resultMsg);

        // 返回
        if (resultMsg.hasSceneEnd()) {
            // 跨服返回
            OperaMaskProto.CrossOperaMaskGameOverMsg.Builder crossMsg = OperaMaskProto.CrossOperaMaskGameOverMsg.newBuilder();
            crossMsg.setActivityId(config.getActivityInfo().getActivityId());
            crossMsg.setType(1);
            crossMsg.setSceneId(gameData.getSceneId());
            crossMsg.setReqMove(respMsg);
            player.sendPacket(CrossProtocol.C2_OPERA_MASK_GAME_OVER_CHANGE_RANK, crossMsg);
        } else {
            // 本服返回
            player.sendPacket(ClientProtocol.U_OPERA_MASK_MOVE_BLOCK, respMsg);
        }
        return 0;
    }

    /**
     * 选择技能
     */
    public int chooseSkill(int skillId) {
        OperaMaskConfig config = OperaMaskMgr.getConfig();
        OperaMaskUserData userData = getUserData();

        // 【校验参数】

        OperaMaskGameData gameData = userData.getGameData();
        if (gameData == null) {
            return GameErrorCode.E_OPERA_MASK_NO_SCENE_PLAYING; // 没有剧目进行中
        }
        List<Integer> chooseSkill = gameData.getChooseSkill();

        OperaMaskSceneConfig sceneConfig = config.getSceneConfigMap().get(gameData.getSceneId());
        if (!sceneConfig.getPaintBoxConfigMap().containsKey(gameData.getBoxLevel())) {
            return GameErrorCode.E_OPERA_MASK_BOX_MAX_LEVEL; // 满级了
        }

        OperaMaskPaintBoxConfig boxConfig = sceneConfig.getPaintBoxConfigMap().get(gameData.getBoxLevel());
        int upgradeCost = getBoxUpgradeCost(config, boxConfig, gameData.getSkillList());
        if (gameData.getBoxValue() < upgradeCost) {
            return GameErrorCode.E_OPERA_MASK_BOX_NO_UPGRADE; // 没有升级
        }

        boolean onlyAddStep = gameData.getChooseSkill().isEmpty(); // 没技能可选，只加步数

        if (!onlyAddStep && !chooseSkill.contains(skillId)) { // 有技能可选时，技能必须是列表内的
            return GameErrorCode.E_OPERA_MASK_SKILL_NO_EXIST;
        }

        // 判断等级上限
        if (!onlyAddStep) {
            Optional<OperaMaskSkill> first = gameData.getSkillList().stream().filter(item -> item.getSkillId() == skillId).findFirst();
            if (first.isPresent()) {
                OperaMaskSkill skill = first.get();
                List<OperaMaskSkillConfig> skillConfigList = config.getSkillConfigMap().get(skill.getSkillId());
                if (skill.getSkillLevel() >= skillConfigList.size()) {
                    return GameErrorCode.E_OPERA_MASK_SKILL_MAX_LEVEL; // 技能等级达上限了
                }
            }
        }

        // 扣消耗
        gameData.setBoxValue(gameData.getBoxValue() - upgradeCost);
        gameData.setBoxLevel(gameData.getBoxLevel() + 1);

        // 加步数
        int addStep = OperaMaskMgr.getBoxUpgradeAddStep(gameData);
        gameData.setLeftStep(gameData.getLeftStep() + addStep);

        // 加技能
        if (!onlyAddStep) {
            Optional<OperaMaskSkill> skillOptional = gameData.getSkillList().stream().filter(item -> item.getSkillId() == skillId).findFirst();
            if (skillOptional.isPresent()) { // 已有技能：升级
                OperaMaskSkill skill = skillOptional.get();
                skill.setSkillLevel(skill.getSkillLevel() + 1);
            } else { // 没有技能：添加新技能
                OperaMaskSkill skill = new OperaMaskSkill(skillId, 1);
                gameData.getSkillList().add(skill);
            }
        }

        // 日志
        int logOldSkillStar = userData.getSkillMaxLevelMap().values().stream().mapToInt(Integer::intValue).sum();
        String oldSkillInfo = JSON.toJSONString(userData.getSkillMaxLevelMap());

        // 更新历史最高技能
        Map<Integer, Integer> skillMaxLevelMap = userData.getSkillMaxLevelMap();
        for (OperaMaskSkill skill : gameData.getSkillList()) {
            if (skillMaxLevelMap.getOrDefault(skill.getSkillId(), 0) < skill.getSkillLevel()) {
                skillMaxLevelMap.put(skill.getSkillId(), skill.getSkillLevel());
            }
        }

        // 日志
        int logNewSkillStar = userData.getSkillMaxLevelMap().values().stream().mapToInt(Integer::intValue).sum();
        String newSkillInfo = JSON.toJSONString(userData.getSkillMaxLevelMap());

        // 刷新可选技能列表
        if (isBoxUpgrade(config, gameData)) {
            gameData.setAllRandomTimes(gameData.getAllRandomTimes() + 1);
            gameData.setChooseSkill(OperaMaskMgr.getRandomSkillList(config, userData, gameData));
        }

        // 成就修改
        int sumLevel = userData.getSkillMaxLevelMap().values().stream().mapToInt(Integer::intValue).sum();
        player.notifyListener(eGamePlayerEventType.OperaMaskSumSkillLevel.getValue(), sumLevel);

        // 处理部分技能效果
        List<OperaMaskSkillEffect> skillEffectList = new ArrayList<>();
        if (!onlyAddStep) {
            Optional<OperaMaskSkill> skill = gameData.getSkillList().stream().filter(item -> item.getSkillId() == skillId).findFirst();
            if (skill.isPresent()) {
                OperaMaskSkill skill1 = skill.get();
                handleChooseSkillEffect(skill1, skillEffectList, userData, config);
            }
        }

        handleSkill22Effect(skillEffectList, userData, config);

        // 返回
        OperaMaskProto.OperaMaskChooseSkillRespMsg.Builder builder = OperaMaskProto.OperaMaskChooseSkillRespMsg.newBuilder();
        builder.setRet(0);
        builder.setUserData(OperaMaskPb.parseOperaMaskUserDataMsg(userData));
        builder.setGameData(OperaMaskPb.parseOperaMaskGameDataMsg(gameData, userData));
        for (OperaMaskSkillEffect skillEffect : skillEffectList) {
            builder.addSkillEffect(OperaMaskPb.parseOperaMaskSkillEffectMsg(skillEffect));
        }
        player.sendPacket(ClientProtocol.U_OPERA_MASK_CHOOSE_SKILL, builder);

        userData.setUpdateOption();

        // 日志
        if (logNewSkillStar > logOldSkillStar) {
            AutoLogMgr.add(new LogOperaMaskSkillStar(
                    getUserId(),
                    config.getActivityInfo().getActivityId(),
                    logOldSkillStar,
                    logNewSkillStar,
                    userData.getSumEnergy(),
                    oldSkillInfo,
                    newSkillInfo,
                    getSumScore()
            ));
        }

        return 0;
    }

    /**
     * 获取累计雅韵值
     */
    private long getSumScore() {
        return operaMaskUserData.getSceneDataMap().values().stream().mapToLong(OperaMaskSceneData::getScore).sum();
    }

    /**
     * 随机技能
     */
    public int randomSkill() {
        OperaMaskConfig config = OperaMaskMgr.getConfig();
        OperaMaskUserData userData = getUserData();

        // 【校验参数】

        OperaMaskGameData gameData = userData.getGameData();
        if (gameData == null) {
            return GameErrorCode.E_OPERA_MASK_NO_SCENE_PLAYING; // 没有剧目进行中
        }

        Map<Integer, OperaMaskPaintBoxConfig> boxConfigMap = config.getSceneConfigMap().get(gameData.getSceneId()).getPaintBoxConfigMap();
        if (!boxConfigMap.containsKey(gameData.getBoxLevel())) {
            return GameErrorCode.E_OPERA_MASK_BOX_MAX_LEVEL; // 满级了
        }

        OperaMaskPaintBoxConfig boxConfig = boxConfigMap.get(gameData.getBoxLevel());
        int upgradeCost = getBoxUpgradeCost(config, boxConfig, gameData.getSkillList());
        if (gameData.getBoxValue() < upgradeCost) {
            return GameErrorCode.E_OPERA_MASK_BOX_NO_UPGRADE; // 没有升级
        }

        int maxSkillCount = OperaMaskMgr.getAllCanRandomSkillList(userData, gameData).size();
        if (maxSkillCount <= OperaMaskMgr.MAX_RANDOM_SKILL_COUNT) {
            return GameErrorCode.E_OPERA_MASK_NO_NEW_SKILL_RANDOM; // 没有别的技能随机了
        }

        // 扣道具
        Property costProp = PropertyHelper.parseStringToProperty(config.getRandomSkillItemId() + "=" + 1);
        if (!player.getModule(CurrencyModule.class).removeCurrency(costProp, eLogMoneyType.OperaMaskActivity, eLogMoneyType.OperaMaskActivityRandomSkill)) {
            return GameErrorCode.E_BAG_ITEM_NO_ENOUGH;
        }

        // 刷新可选技能列表
        gameData.setAllRandomTimes(gameData.getAllRandomTimes() + 1);
        gameData.setChooseSkill(OperaMaskMgr.getRandomSkillList(config, userData, gameData));

        // 加付费重随次数
        gameData.setRandomTimes(gameData.getRandomTimes() + 1);

        // 返回
        OperaMaskProto.OperaMaskChooseSkillRespMsg.Builder builder = OperaMaskProto.OperaMaskChooseSkillRespMsg.newBuilder();
        builder.setRet(0);
        builder.setUserData(OperaMaskPb.parseOperaMaskUserDataMsg(userData));
        builder.setGameData(OperaMaskPb.parseOperaMaskGameDataMsg(gameData, userData));
        player.sendPacket(ClientProtocol.U_OPERA_MASK_CHOOSE_SKILL, builder);

        userData.setUpdateOption();

        return 0;
    }

    /**
     * 取消关卡
     */
    public int cancelScene() {
        OperaMaskConfig config = OperaMaskMgr.getConfig();
        OperaMaskUserData userData = getUserData();

        OperaMaskGameData gameData = userData.getGameData();
        if (gameData == null) {
            return GameErrorCode.E_OPERA_MASK_NO_SCENE_PLAYING;
        }

        // 游戏结束
        OperaMaskProto.OperaMaskSceneEndMsg.Builder endBuilder = handleGameOver(config, userData, gameData);

        // 返回值
        OperaMaskProto.OperaMaskCancelSceneRespMsg.Builder resp = OperaMaskProto.OperaMaskCancelSceneRespMsg.newBuilder();
        resp.setRet(0);
        resp.setSceneEnd(endBuilder);

        // 跨服返回
        OperaMaskProto.CrossOperaMaskGameOverMsg.Builder crossMsg = OperaMaskProto.CrossOperaMaskGameOverMsg.newBuilder();
        crossMsg.setActivityId(config.getActivityInfo().getActivityId());
        crossMsg.setType(2);
        crossMsg.setSceneId(gameData.getSceneId());
        crossMsg.setReqCancel(resp);
        player.sendPacket(CrossProtocol.C2_OPERA_MASK_GAME_OVER_CHANGE_RANK, crossMsg);

        userData.setUpdateOption();

        return 0;
    }

    /**
     * 新关卡删掉new
     */
    public int finishUnlockShow(int sceneId) {
        OperaMaskUserData userData = getUserData();

        // 判断状态
        Map<Integer, OperaMaskSceneData> sceneDataMap = userData.getSceneDataMap();
        OperaMaskSceneData sceneData = sceneDataMap.get(sceneId);
        if (sceneData == null) {
            return GameErrorCode.E_OPERA_MASK_SCENE_NO_EXIST;
        }
        if (sceneData.getStatus() == OperaMaskSceneData.STATUS_LOCK) {
            return GameErrorCode.E_OPERA_MASK_SCENE_NO_UNLOCK;
        }

        // 重复请求，直接返回
        if (sceneData.getStatus() == OperaMaskSceneData.STATUS_UNLOCK) {
            return 0;
        }

        // 修改数据
        sceneData.setStatus(OperaMaskSceneData.STATUS_UNLOCK);

        userData.setUpdateOption();

        return 0;
    }

    /**
     * 消除提示
     */
    public void eliminateTip() {
        if (getUserData().getGameData() == null) {
            return;
        }

        OperaMaskGameData gameData = getUserData().getGameData();
        boolean canObliqueMove = OperaMaskSkillLogic.getSkill04Effect(gameData.getSkillList(), OperaMaskMgr.getConfig()) > 0;
        boolean canObliqueEliminate = OperaMaskSkillLogic.getSkill08Effect(gameData.getSkillList());
        OperaMaskEliminateTip eliminateTip = OperaMaskEliminateLogic.getEliminateTip(gameData.getBlockList(), canObliqueMove, canObliqueEliminate);
        if (eliminateTip == null) {
            log.error("opera mask player {}  can not eliminate!!!", getUserId());
            return;
        }

        OperaMaskProto.OperaMaskGetTipsRespMsg.Builder builder = OperaMaskProto.OperaMaskGetTipsRespMsg.newBuilder();
        builder.setRet(0);
        builder.setFrom(OperaMaskPb.parseOperaMaskBlockMsg(eliminateTip.getFrom()));
        if (eliminateTip.getTo() != null) {
            builder.setTo(OperaMaskPb.parseOperaMaskBlockMsg(eliminateTip.getTo()));
        }
        for (OperaMaskBlock block : eliminateTip.getEliminateSet()) {
            builder.addBlockList(OperaMaskPb.parseOperaMaskBlockMsg(block));
        }
        player.sendPacket(ClientProtocol.U_OPERA_MASK_GET_TIPS, builder);
    }

    /**
     * 获得斜向消除后检查方块
     */
    public int checkBlock() {
        OperaMaskConfig config = OperaMaskMgr.getConfig();
        OperaMaskUserData userData = getUserData();

        OperaMaskGameData gameData = userData.getGameData();
        if (gameData == null) {
            return -1;
        }

        OperaMaskEliminateParam param = new OperaMaskEliminateParam();
        param.setConfig(config);
        param.setGameData(gameData.clone()); // clone防止报错数据炸了
        param.setFromX(0);
        param.setFromY(0);
        param.setToX(0);
        param.setToY(0);
        param.setFirstMove(false);
        OperaMaskEliminateResult result = OperaMaskEliminateLogic.moveBlock(param, config, false);
        if (result.getRet() != 0) {
            return -1;
        }

        // 消除
        OperaMaskProto.OperaMaskResultMsg.Builder resultMsg = handleMoveResult(config, userData, gameData, result, false);

        // 返回
        OperaMaskProto.OperaMaskCheckBlockRespMsg.Builder builder = OperaMaskProto.OperaMaskCheckBlockRespMsg.newBuilder();
        builder.setRet(0);
        builder.setResult(resultMsg);
        player.sendPacket(ClientProtocol.U_OPERA_MASK_CHECK_BLOCK, builder);
        return 0;
    }

    /**
     * 能否购买战令
     */
    public boolean canRechargeToken(MallInfo mallInfo) {

        // 判断时间
        ActivityInfo activityInfo = OperaMaskMgr.getActivityInfo();
        if (!ActivityMgr.activityInShowTime(activityInfo)) {
            return false;
        }

        // 拿数据
        OperaMaskUserData userData = getUserData();
        if (userData == null || userData.getActivityId() != activityInfo.getActivityId()) {
            return false;
        }

        // 拿战令ID
        int tokenId;
        if (mallInfo.getShopType() == eShopType.ChineseChessSilverToken.getValue()) {
            tokenId = 1;
        } else if (mallInfo.getShopType() == eShopType.ChineseChessGoldToken.getValue()) {
            tokenId = 2;
        } else {
            return false;
        }

        // 是否重复购买
        if (userData.getTokenList().contains(tokenId)) {
            return false;
        }

        return true;
    }

    /**
     * 购买战令
     */
    public void rechargeToken(MallInfo mallInfo) {

        // 拿数据
        OperaMaskUserData userData = getUserData();
        if (userData == null) {
            log.error("userId {} rechargeToken error! no opera mask user data! mallId {} shopType {} category {}.",
                    getUserId(), mallInfo.getMallId(), mallInfo.getShopType(), mallInfo.getCategory());
            return;
        }

        // 拿战令ID
        int tokenId;
        if (mallInfo.getShopType() == eShopType.ChineseChessSilverToken.getValue()) {
            tokenId = 1;
        } else if (mallInfo.getShopType() == eShopType.ChineseChessGoldToken.getValue()) {
            tokenId = 2;
        } else {
            log.error("userId {} rechargeToken error! no token type! mallId {} shopType {} category {}.",
                    getUserId(), mallInfo.getMallId(), mallInfo.getShopType(), mallInfo.getCategory());
            return;
        }

        // 是否重复购买
        List<Integer> tokenList = userData.getTokenList();
        if (tokenList.contains(tokenId)) {
            log.error("userId {} rechargeToken error! repeated buy! mallId {} shopType {} category {}.",
                    getUserId(), mallInfo.getMallId(), mallInfo.getShopType(), mallInfo.getCategory());
            return;
        }

        // 购买成功
        ArrayList<Integer> clone = new ArrayList<>(tokenList);
        clone.add(tokenId);
        userData.setTokenList(clone);
        userData.setUpdateOption();

        // 同步购买状态
        syncUserData();

        // 成就
        try {
            notifyScoreChangeCondition(OperaMaskMgr.getConfig(), userData, null);
        } catch (Exception e) {
            log.error("error : ", e);
        }
    }

    /**
     * 更新成就、榜单
     */
    public void notifyScoreChangeCondition(OperaMaskConfig config, OperaMaskUserData userData, Integer sceneId) {
        // 战令
        player.notifyListener(eGamePlayerEventType.OperaMaskBronzeToken.getValue(), OperaMaskSumScoreArgs.create(userData));
        if (userData.getTokenList().contains(1)) {
            player.notifyListener(eGamePlayerEventType.OperaMaskSilverToken.getValue(), OperaMaskSumScoreArgs.create(userData));
        }
        if (userData.getTokenList().contains(2)) {
            player.notifyListener(eGamePlayerEventType.OperaMaskGoldToken.getValue(), OperaMaskSumScoreArgs.create(userData));
        }
        // 剧目评级
        for (OperaMaskSceneData sceneData : userData.getSceneDataMap().values()) {
            if (sceneId != null && sceneData.getId() != sceneId) {
                continue;
            }
            OperaMaskScenePingJiArgs args = new OperaMaskScenePingJiArgs(
                    sceneData.getId(),
                    OperaMaskMgr.calcScenePingJi(config, sceneData.getId(), sceneData.getScore())
            );
            player.notifyListener(eGamePlayerEventType.OperaMaskScenePingJi.getValue(), args);
        }
        // 榜单
        player.notifyListener(eGamePlayerEventType.OperaMaskSumScoreRank.getValue(), OperaMaskSumScoreArgs.create(userData));
    }

    // ==================== 消除结果处理 =====================

    // 处理每轮结果
    private OperaMaskProto.OperaMaskRoundResultMsg.Builder parseRoundResultMsg(OperaMaskRoundResult roundResult) {
        OperaMaskProto.OperaMaskRoundResultMsg.Builder roundBuilder = OperaMaskProto.OperaMaskRoundResultMsg.newBuilder();
        roundBuilder.setType(roundResult.getType());
        for (OperaMaskStageResult stageResult : roundResult.getStageResultList()) {
            OperaMaskProto.OperaMaskStageResultMsg.Builder stageBuilder = OperaMaskProto.OperaMaskStageResultMsg.newBuilder();
            for (OperaMaskBlock block : stageResult.getBlockList()) {
                stageBuilder.addBlockList(OperaMaskPb.parseOperaMaskBlockMsg(block));
            }
            for (OperaMaskBoom boom : stageResult.getBoomList()) {
                if (boom.getType() != eOperaMaskBoomType.Empty.getValue()) {
                    stageBuilder.addBoomList(OperaMaskPb.parseOperaMaskBoomMsg(boom));
                }
            }
            roundBuilder.addStageResult(stageBuilder);
        }
        for (OperaMaskBlock createBomb : roundResult.getCreateBomb()) {
            roundBuilder.addCreateBomb(OperaMaskPb.parseOperaMaskBlockMsg(createBomb));
        }
        for (List<OperaMaskBlock> list : roundResult.getListList()) {
            for (OperaMaskBlock block : list) {
                roundBuilder.addBlockList(OperaMaskPb.parseOperaMaskBlockMsg(block));
            }
        }
        for (OperaMaskSkillEffect effect : roundResult.getSkillEffectList()) {
            roundBuilder.addSkillEffect(OperaMaskPb.parseOperaMaskSkillEffectMsg(effect));
        }
        for (OperaMaskSkillEffect effect : roundResult.getSkillEffectListBeforeDrop()) {
            roundBuilder.addSkillEffectBeforeDrop(OperaMaskPb.parseOperaMaskSkillEffectMsg(effect));
        }
        return roundBuilder;
    }

    // 处理消除结果
    private OperaMaskProto.OperaMaskResultMsg.Builder handleMoveResult(OperaMaskConfig config,
                                                                       OperaMaskUserData userData,
                                                                       OperaMaskGameData gameData,
                                                                       OperaMaskEliminateResult result,
                                                                       boolean reduceStep) {

        OperaMaskSceneConfig sceneConfig = config.getSceneConfigMap().get(gameData.getSceneId());

        boolean oldBoxUpgrade = isBoxUpgrade(config, gameData);

        // 每个方块雅韵值配置
        int tokenYaYun = userData.getTokenList().contains(1) ? config.getTokenYaYun() : 0;
        int baseScore = config.getSingConfigMap().get(userData.getSingLevel()).getBaseYaYun() + tokenYaYun;
        List<Integer> sceneEffect1004 = OperaMaskSkillLogic.getSceneSkillEffectList(sceneConfig, eOperaMaskSkillType.Skill1004);
        if (sceneEffect1004 != null) {
            baseScore = BigDecimal.valueOf(baseScore)
                    .multiply(BigDecimal.valueOf(1000 - sceneEffect1004.get(1)))
                    .divide(BigDecimal.valueOf(1000), 0, BigDecimal.ROUND_DOWN).intValue();
        }
        // 技能雅韵值加成值
        long skillScoreAddition = 0L;
        // 结束后技能效果
        List<OperaMaskSkillEffect> endEffectList = new ArrayList<>();

        // 长坂坡剧目效果, 消除油彩可获得其倍数的油彩盒进度
        int boxValueAddByScene = 0;
        List<Integer> sceneEffect1005 = OperaMaskSkillLogic.getSceneSkillEffectList(sceneConfig, eOperaMaskSkillType.Skill1005);

        // 处理消除结果
        int blockCount = 0;
        Map<Integer, Integer> colorCountMap = new HashMap<>();
        // 油彩颜色倍数
        Map<Integer, Integer> colorMulMap = new HashMap<>();
        long skill15Addition = 0L;
        List<Integer> skill15Effect = OperaMaskSkillLogic.getSkill15Effect(gameData.getSkillList(), config);
        for (OperaMaskRoundResult roundResult : result.getRoundResultList()) {
            for (OperaMaskStageResult stageResult : roundResult.getStageResultList()) {
                blockCount += stageResult.getBlockList().size();
                for (OperaMaskBlock block : stageResult.getBlockList()) {
                    // 统计油彩颜色数
                    colorCountMap.put(block.getColor(), 1 + colorCountMap.getOrDefault(block.getColor(), 0));
                    // 设置方块积分
                    int multi = block.getTotalMul();
                    block.setScore(baseScore * multi);

                    if (null != sceneEffect1005 && multi > 1) {
                        boxValueAddByScene += multi - 1;
                        if (Config.isDebug()) {
                            logger.info("长坂坡剧目效果 颜色({})油彩获得倍数+{}={} ", block.getColor(), multi - 1, boxValueAddByScene);
                        }
                        int newMulti1005 = 0;
                        // 长坂坡剧目效果, 消除油彩可获得其倍数的油彩盒进度
                        if (colorMulMap.containsKey(block.getColor())) {
                            newMulti1005 = colorMulMap.get(block.getColor()) + multi - 1;
                        } else {
                            newMulti1005 = multi - 1;
                        }
                        colorMulMap.put(block.getColor(), newMulti1005);
                    }
                }
            }
        }
        skillScoreAddition += skill15Addition;
        // 技能15加倍
        if (skill15Effect.get(0) > 0) {
            OperaMaskSkillEffect effect = new OperaMaskSkillEffect(eOperaMaskSkillType.Skill15.getValue());
            List<OperaMaskBlock> bottomBlockList = result.getListList().get(0);
            for (OperaMaskBlock block : bottomBlockList) {
                int oldMul15 = block.getMul15();
                int newMul15 = oldMul15 * skill15Effect.get(0);
                newMul15 = Math.min(newMul15, skill15Effect.get(1));
                block.setMul15(newMul15);
                effect.getBlockList().add(block.clone()); // 加效果值
            }
            endEffectList.add(effect);
        }

        // 雅韵值计算
        long score = 0;
        for (OperaMaskRoundResult roundResult : result.getRoundResultList()) {
            for (OperaMaskStageResult stageResult : roundResult.getStageResultList()) {
                for (OperaMaskBlock block : stageResult.getBlockList()) {
                    score += block.getScore();
                }
            }
        }

        int skill05Effect = OperaMaskSkillLogic.getSkill05Effect(gameData.getSkillList(), config);
        if (skill05Effect > 0) {
            long tmpValue = BigDecimal.valueOf(score).multiply(BigDecimal.valueOf(blockCount * skill05Effect)).divide(BigDecimal.valueOf(1000), 0, BigDecimal.ROUND_DOWN).longValue();
            skillScoreAddition += tmpValue;
            OperaMaskSkillEffect effect = new OperaMaskSkillEffect(eOperaMaskSkillType.Skill05.getValue());
            effect.setValueList(Collections.singletonList((int) tmpValue));
            endEffectList.add(effect);
        }

        int skill06Effect = OperaMaskSkillLogic.getSkill06Effect(gameData.getSkillList(), config);
        if (skill06Effect > 0) {
            long tmpValue = BigDecimal.valueOf(score).multiply(BigDecimal.valueOf(gameData.getBoxLevel() * skill06Effect)).divide(BigDecimal.valueOf(1000), 0, BigDecimal.ROUND_DOWN).longValue();
            skillScoreAddition += tmpValue;
            OperaMaskSkillEffect effect = new OperaMaskSkillEffect(eOperaMaskSkillType.Skill06.getValue());
            effect.setValueList(Collections.singletonList((int) tmpValue));
            endEffectList.add(effect);
        }

        int skill11Effect = OperaMaskSkillLogic.getSkill11Effect(gameData.getSkillList(), config);
        if (skill11Effect > 0) {
            int totalStar = gameData.getSkillList().stream().mapToInt(OperaMaskSkill::getSkillLevel).sum();
            long tmpValue = BigDecimal.valueOf(score).multiply(BigDecimal.valueOf(totalStar * skill11Effect)).divide(BigDecimal.valueOf(1000), 0, BigDecimal.ROUND_DOWN).longValue();
            skillScoreAddition += tmpValue;
            OperaMaskSkillEffect effect = new OperaMaskSkillEffect(eOperaMaskSkillType.Skill11.getValue());
            effect.setValueList(Collections.singletonList((int) tmpValue));
            endEffectList.add(effect);
        }

        List<Integer> skill18Effect = OperaMaskSkillLogic.getSkill18Effect(gameData.getSkillList(), config);
        if (skill18Effect != null) {
            int boomTimes = 0;
            for (OperaMaskRoundResult roundResult : result.getRoundResultList()) {
                for (OperaMaskStageResult stageResult : roundResult.getStageResultList()) {
                    boomTimes += stageResult.getBoomList().size();
                }
            }
            if (boomTimes > 0 && skill18Effect.size() >= 2) {
                // 每回合最多获得+{1}%
                int boomMulti = boomTimes * skill18Effect.get(0);
                if (Config.isDebug()) {
                    logger.info("技能18 消除加成{}  最多{} ", boomMulti, skill18Effect.get(1));
                }
                boomMulti = boomMulti > skill18Effect.get(1) ? skill18Effect.get(1) : boomMulti;

                long tmpValue = BigDecimal.valueOf(score).multiply(BigDecimal.valueOf(boomMulti)).divide(BigDecimal.valueOf(1000), 0, BigDecimal.ROUND_DOWN).longValue();
                skillScoreAddition += tmpValue;
                OperaMaskSkillEffect effect = new OperaMaskSkillEffect(eOperaMaskSkillType.Skill18.getValue());
                effect.setValueList(Collections.singletonList((int) tmpValue));
                endEffectList.add(effect);
            }
        }

        // 斜消技能效果：只有这里面的技能能加油彩盒进度
        Set<Integer> skill08ColorSet = null;
        if (gameData.getSkillList().stream().anyMatch(item -> item.getSkillId() == eOperaMaskSkillType.Skill08.getValue())) {
            // 斜消技能效果配置
            Integer param = config.getSkillConfigMap().get(eOperaMaskSkillType.Skill08.getValue()).get(0).getSkillParamList().get(0);
            char[] charParam = param.toString().toCharArray();
            // 斜消技能装备后，只有指定颜色可以加油彩盒进度（红色技能效果除外）
            skill08ColorSet = new HashSet<>();
            skill08ColorSet.add(0);
            for (char c : charParam) {
                skill08ColorSet.add(c - '0');
            }
        }

        // 计算油彩盒进度
        int boxValue = blockCount;
        if (skill08ColorSet != null) {
            // 重新计算值
            boxValue = 0;
            boxValueAddByScene = 0;
            for (Map.Entry<Integer, Integer> entry : colorCountMap.entrySet()) {
                if (skill08ColorSet.contains(entry.getKey())) {
                    boxValue += entry.getValue();

                    if (colorMulMap.containsKey(entry.getKey())) {
                        // 长坂坡剧目效果, 消除油彩可获得其倍数的油彩盒进度
                        boxValueAddByScene += colorMulMap.get(entry.getKey());
                        if (Config.isDebug()) {
                            logger.info("技能08,重新计算长坂坡剧目效果 颜色({})油彩获得倍数+{}={} ", entry.getKey(), colorMulMap.get(entry.getKey()), boxValueAddByScene);
                        }
                    }
                }
            }
        }

        int boxValueAddition = 0;
        int skill12Effect = OperaMaskSkillLogic.getSkill12Effect(gameData.getSkillList(), config);
        if (skill12Effect > 0 && colorCountMap.containsKey(OperaMaskMgr.RED_COLOR)) {
            // 没有技能12 || 技能12生效后红色油彩依然能加进度
            if (skill08ColorSet == null || skill08ColorSet.contains(OperaMaskMgr.RED_COLOR)) {
                int tmpValue = colorCountMap.get(OperaMaskMgr.RED_COLOR) * skill12Effect;
                boxValueAddition += tmpValue;
                OperaMaskSkillEffect effect = new OperaMaskSkillEffect(eOperaMaskSkillType.Skill12.getValue());
                effect.setValueList(Collections.singletonList(tmpValue));
                endEffectList.add(effect);
            }
        }
//        List<Integer> sceneEffect1005 = OperaMaskSkillLogic.getSceneSkillEffectList(sceneConfig, eOperaMaskSkillType.Skill1005);
//        if (sceneEffect1005 != null) {
//            boxValueAddition += sceneEffect1005.get(0) / 1000 * blockCount;
//        }

        // 剧目技能加步数
        int sceneSkillAddStep = 0;
        List<Integer> sceneEffect1006 = OperaMaskSkillLogic.getSceneSkillEffectList(sceneConfig, eOperaMaskSkillType.Skill1006);
        if (sceneEffect1006 != null) {
            if (blockCount == 0 && gameData.getSkillAddStep() == 0) {
                sceneSkillAddStep = sceneEffect1006.get(0) / 1000;
            }
        }

        // 设置结束后技能
        result.addEndEffectList(endEffectList);

        // 设置新值
        if (Config.isDebug()) {
            logger.info("实际彩盒进度 普通增加={}, 技能12增加={}, 长坂坡剧目增加={} ", boxValue, boxValueAddition, boxValueAddByScene);
        }
        gameData.setBoxValue(gameData.getBoxValue() + boxValue + boxValueAddition + boxValueAddByScene);
        gameData.setScore(gameData.getScore() + score + skillScoreAddition);
        if (reduceStep) {
            // 剧目步数变化
            if (gameData.getSkillAddStep() > 0) {
                gameData.setSkillAddStep(gameData.getSkillAddStep() - 1);
            } else {
                gameData.setLeftStep(gameData.getLeftStep() - 1);
                gameData.setUseStep(gameData.getUseStep() + 1);
            }
            // 剧目步数效果
            if (sceneSkillAddStep > 0) {
                gameData.setSkillAddStep(gameData.getSkillAddStep() + sceneSkillAddStep);
            }
        }
        gameData.setBlockList(result.getListList());

        // 修改历史最高分
        OperaMaskSceneData historySceneData = userData.getSceneDataMap().get(gameData.getSceneId());
        if (gameData.getScore() > historySceneData.getScore()) {
            historySceneData.setScore(gameData.getScore());
            historySceneData.setSkill(JSON.toJSONString(gameData.getSkillList()));
            historySceneData.setStep(gameData.getUseStep());
            historySceneData.setSkValue(OperaMaskMgr.calcSkillValue(gameData.getScore(), userData.getSingLevel()));

            topScoreChange(config, userData, historySceneData);
        }
        // 修改技巧分
        int skillValue = OperaMaskMgr.calcSkillValue(gameData.getScore(), userData.getSingLevel());
        if (skillValue > historySceneData.getSkValue2()) {
            historySceneData.setScore2(gameData.getScore());
            historySceneData.setSkill2(JSON.toJSONString(gameData.getSkillList()));
            historySceneData.setStep2(gameData.getUseStep());
            historySceneData.setSkValue2(OperaMaskMgr.calcSkillValue(gameData.getScore(), userData.getSingLevel()));

            // topSkillValueChange(config, userData, historySceneData);
        }

        // 是否升级，升级要重随技能
        if (!oldBoxUpgrade && isBoxUpgrade(config, gameData)) {
            gameData.setAllRandomTimes(gameData.getAllRandomTimes() + 1);
            gameData.setChooseSkill(OperaMaskMgr.getRandomSkillList(config, userData, gameData));
        }

        // 判断游戏结束
        OperaMaskProto.OperaMaskSceneEndMsg.Builder endBuilder = null;
        if (isGameOver(config, gameData)) {
            endBuilder = handleGameOver(config, userData, gameData);
        }

        userData.setUpdateOption();

        // 【组装返回值】
        // 1. boomList：彩虹炸弹 + 普通炸弹/普通油彩
        OperaMaskProto.OperaMaskResultMsg.Builder builder = OperaMaskProto.OperaMaskResultMsg.newBuilder();
        if (result.getRoundResultList().size() > 0 && result.getRoundResultList().get(0).getStageResultList().size() > 0) {
            OperaMaskStageResult firstStageResult = result.getRoundResultList().get(0).getStageResultList().get(0);
            for (int i = 0; i < firstStageResult.getBoomList().size(); i++) {
                OperaMaskBoom boom = firstStageResult.getBoomList().get(i);
                if (boom.getType() == eOperaMaskBoomType.ColorAndNormal.getValue() || boom.getType() == eOperaMaskBoomType.ColorAndBomb.getValue()) {
                    builder.addBoomList(OperaMaskPb.parseOperaMaskBoomMsg(boom));
                    firstStageResult.getBoomList().remove(i--);
                }
            }
        }
        // 2. light：正常消除0，加13、14、1、2的效果
        if (builder.getBoomListList().stream().noneMatch(item -> item.getType() == eOperaMaskBoomType.ColorAndColor.getValue())) {
            OperaMaskSkillEffect lightEliminate = new OperaMaskSkillEffect(0);
            lightEliminate.setBlockList(result.getLightEliminateList());
            builder.addLight(OperaMaskPb.parseOperaMaskLightEffectMsg(lightEliminate));
            for (OperaMaskSkillEffect lightSkill : result.getLightSkillList()) {
                OperaMaskProto.OperaMaskLightEffectMsg.Builder effectMsg = OperaMaskPb.parseOperaMaskLightEffectMsg(lightSkill);
                builder.addLight(effectMsg);
            }
        }
        // 3. OperaMaskRoundResultMsg：正常消除跟爆炸的信息
        for (OperaMaskRoundResult roundResult : result.getRoundResultList()) {
            OperaMaskProto.OperaMaskRoundResultMsg.Builder roundBuilder = parseRoundResultMsg(roundResult);
            builder.addRoundResult(roundBuilder);
        }
        // 4. endSkill：盘面稳定后的技能效果
        for (OperaMaskSkillEffect skillEffect : result.getEndEffectList()) {
            builder.addEndSkill(OperaMaskPb.parseOperaMaskSkillEffectMsg(skillEffect));
        }
        // 其他返回数据
        builder.setGameData(OperaMaskPb.parseOperaMaskGameDataMsg(gameData, userData));
        builder.setUserData(OperaMaskPb.parseOperaMaskUserDataMsg(userData));
        if (endBuilder != null) {
            builder.setSceneEnd(endBuilder);
        }

        // 日志
        StringBuilder sb = new StringBuilder();
        for (OperaMaskSkill skill : gameData.getSkillList()) {
            sb.append(skill.getSkillId());
            sb.append("=");
            sb.append(skill.getSkillLevel());
            sb.append(";");
        }
        AutoLogMgr.add(new LogOperaMaskEliminate(
                userData.getUserId(),
                userData.getActivityId(),
                gameData.getSceneId(),
                userData.getSingLevel(),
                gameData.getBoxLevel(),
                sb.toString(),
                gameData.getUseStep(),
                gameData.getLeftStep(),
                blockCount,
                score + skillScoreAddition,
                gameData.getScore(),
                boxValue + boxValueAddition + boxValueAddByScene,
                gameData.getBoxValue()
        ));

        return builder;
    }

    // 修改历史最高分
    private void topScoreChange(OperaMaskConfig config, OperaMaskUserData userData, OperaMaskSceneData sceneData) {
        // 解锁关卡
        for (OperaMaskSceneConfig tmpConfig : config.getSceneConfigMap().values()) {
            tryUnlockScene(userData, tmpConfig.getSceneId());
        }
        // 排行榜、成就
        notifyScoreChangeCondition(config, userData, sceneData.getId());
        // 剧目雅韵榜
        player.sendPacket(CrossProtocol.C2_OPERA_MASK_UPDATE_SCENE_RANK,
                OperaMaskProto.CrossOperaMaskUpdateSceneRankReqMsg.newBuilder()
                        .setActivityId(config.getActivityInfo().getActivityId())
                        .setSceneId(sceneData.getId())
                        .setRankMsg(OperaMaskProto.OperaMaskGetSceneRankTemp.newBuilder()
                                .setUserId(getUserId())
                                .setPlayerBaseData(PlayerPb.parsePlayerBaseTempMsg(player.getUserInfo()))
                                .setValue(String.valueOf(sceneData.getScore()))
                                .setStep(sceneData.getStep())
                                .addAllSkillList(OperaMaskPb2.parseOperaMaskSkillMsgList(sceneData.getSkill()))
                                .setSingLevel(userData.getSingLevel())
                                .setSkillValue(sceneData.getSkValue())
                                .setType(0)));

    }

    // 修改历史技巧分
    private void topSkillValueChange(OperaMaskConfig config, OperaMaskUserData userData, OperaMaskSceneData sceneData) {
        // 剧目技巧榜
        player.sendPacket(CrossProtocol.C2_OPERA_MASK_UPDATE_SCENE_RANK,
                OperaMaskProto.CrossOperaMaskUpdateSceneRankReqMsg.newBuilder()
                        .setActivityId(config.getActivityInfo().getActivityId())
                        .setSceneId(sceneData.getId())
                        .setRankMsg(OperaMaskProto.OperaMaskGetSceneRankTemp.newBuilder()
                                .setUserId(getUserId())
                                .setPlayerBaseData(PlayerPb.parsePlayerBaseTempMsg(player.getUserInfo()))
                                .setValue(String.valueOf(sceneData.getScore2()))
                                .setStep(sceneData.getStep2())
                                .addAllSkillList(OperaMaskPb2.parseOperaMaskSkillMsgList(sceneData.getSkill2()))
                                .setSingLevel(userData.getSingLevel())
                                .setSkillValue(sceneData.getSkValue2())
                                .setType(1)));
    }

    private boolean isNewRecord(OperaMaskGameData gameData) {
        if (null == gameData) {
            return false;
        }

        OperaMaskSceneData historySceneData = getUserData().getSceneDataMap().get(gameData.getSceneId());
        if (null == historySceneData) {
            return true;
        }
        return historySceneData.getScore() < gameData.getScore();
    }


    /**
     * 油彩升级,选择技能,技能生效后->技能22效果
     */
    private void handleSkill22Effect(List<OperaMaskSkillEffect> skillEffectList, OperaMaskUserData userData, OperaMaskConfig config) {
        OperaMaskGameData gameData = userData.getGameData();
        //技能22 油彩盒升级后，最高倍数的油彩将吸收场上所有的倍数并*n。
        int skill22Effect = OperaMaskSkillLogic.getSkill22Effect(gameData.getSkillList(), config);
        if (skill22Effect == 0) {
            return;
        }
        OperaMaskEliminateLogic.handleSkill22(gameData.getBlockList(), skillEffectList, gameData, skill22Effect);
    }
}
